/// [auth checker declaration]
#include "auth_digest.hpp"
#include "user_info.hpp"

#include "sql/queries.hpp"

#include <algorithm>
#include <optional>
#include <string_view>

#include <userver/http/common_headers.hpp>
#include <userver/server/handlers/auth/digest/auth_checker_base.hpp>
#include <userver/server/handlers/auth/digest/auth_checker_settings_component.hpp>
#include <userver/storages/postgres/cluster.hpp>
#include <userver/storages/postgres/cluster_types.hpp>
#include <userver/storages/postgres/component.hpp>
#include <userver/storages/postgres/result_set.hpp>
#include <userver/storages/secdist/component.hpp>
#include <userver/utils/datetime.hpp>

namespace samples::digest_auth {

using UserData = server::handlers::auth::digest::UserData;
using HA1 = server::handlers::auth::digest::UserData::HA1;
using SecdistConfig = storages::secdist::SecdistConfig;
using TimePoint = std::chrono::time_point<std::chrono::system_clock>;

class AuthChecker final : public server::handlers::auth::digest::AuthCheckerBase {
public:
    using AuthCheckResult = server::handlers::auth::AuthCheckResult;
    using AuthDigestSettings = server::handlers::auth::digest::AuthCheckerSettings;

    AuthChecker(
        const AuthDigestSettings& digest_settings,
        std::string realm,
        const SecdistConfig& secdist_config,
        storages::postgres::ClusterPtr pg_cluster
    )
        : server::handlers::auth::digest::AuthCheckerBase(digest_settings, std::move(realm), secdist_config),
          pg_cluster_(std::move(pg_cluster)),
          nonce_ttl_(digest_settings.nonce_ttl)
    {}

    std::optional<UserData> FetchUserData(const std::string& username) const override;

    void SetUserData(
        const std::string& username,
        const std::string& nonce,
        std::int64_t nonce_count,
        TimePoint nonce_creation_time
    ) const override;

    void PushUnnamedNonce(std::string nonce) const override;

    std::optional<TimePoint> GetUnnamedNonceCreationTime(const std::string& nonce) const override;

private:
    storages::postgres::ClusterPtr pg_cluster_;
    const std::chrono::milliseconds nonce_ttl_;
};
/// [auth checker declaration]

/// [auth checker definition 1]
std::optional<UserData> AuthChecker::FetchUserData(const std::string& username) const {
    const storages::postgres::ResultSet res =
        pg_cluster_->Execute(storages::postgres::ClusterHostType::kSlave, uservice_dynconf::sql::kSelectUser, username);

    if (res.IsEmpty()) {
        return std::nullopt;
    }

    auto user_db_info = res.AsSingleRow<UserDbInfo>(storages::postgres::kRowTag);
    return UserData{
        HA1{user_db_info.ha1},
        user_db_info.nonce,
        user_db_info.timestamp.GetUnderlying(),
        user_db_info.nonce_count
    };
}
/// [auth checker definition 1]

/// [auth checker definition 2]
void AuthChecker::SetUserData(
    const std::string& username,
    const std::string& nonce,
    std::int64_t nonce_count,
    TimePoint nonce_creation_time
) const {
    pg_cluster_->Execute(
        storages::postgres::ClusterHostType::kMaster,
        uservice_dynconf::sql::kUpdateUser,
        nonce,
        storages::postgres::TimePointTz{nonce_creation_time},
        nonce_count,
        username
    );
}
/// [auth checker definition 2]

/// [auth checker definition 3]
void AuthChecker::PushUnnamedNonce(std::string nonce) const {
    auto res = pg_cluster_->Execute(
        storages::postgres::ClusterHostType::kMaster,
        uservice_dynconf::sql::kInsertUnnamedNonce,
        storages::postgres::TimePointTz{utils::datetime::Now() - nonce_ttl_},
        nonce,
        storages::postgres::TimePointTz{utils::datetime::Now()}
    );
}
/// [auth checker definition 3]

/// [auth checker definition 4]
std::optional<TimePoint> AuthChecker::GetUnnamedNonceCreationTime(const std::string& nonce) const {
    auto res =
        pg_cluster_
            ->Execute(storages::postgres::ClusterHostType::kSlave, uservice_dynconf::sql::kSelectUnnamedNonce, nonce);

    if (res.IsEmpty()) {
        return std::nullopt;
    }

    return res.AsSingleRow<storages::postgres::TimePointTz>().GetUnderlying();
}
/// [auth checker definition 4]

/// [auth checker factory definition]
CheckerFactory::CheckerFactory(const components::ComponentContext& context)
    : digest_auth_settings_(context.FindComponent<server::handlers::auth::digest::AuthCheckerSettingsComponent>()
                                .GetSettings()),
      secdist_(context.FindComponent<components::Secdist>().GetStorage()),
      pg_cluster_(context.FindComponent<components::Postgres>("auth-database").GetCluster())
{}

server::handlers::auth::AuthCheckerBasePtr CheckerFactory::MakeAuthChecker(
    const server::handlers::auth::HandlerAuthConfig& auth_config
) const {
    return std::make_shared<
        AuthChecker>(digest_auth_settings_, auth_config["realm"].As<std::string>({}), secdist_.Get(), pg_cluster_);
}
/// [auth checker factory definition]

CheckerProxyFactory::CheckerProxyFactory(const components::ComponentContext& context)
    : digest_auth_settings_(
          context
              .FindComponent<server::handlers::auth::digest::AuthCheckerSettingsComponent>(
                  "auth-"
                  "digest-"
                  "checker-"
                  "settings-"
                  "proxy"
              )
              .GetSettings()
      ),
      secdist_(context.FindComponent<components::Secdist>().GetStorage()),
      pg_cluster_(context.FindComponent<components::Postgres>("auth-database").GetCluster())
{}

server::handlers::auth::AuthCheckerBasePtr CheckerProxyFactory::MakeAuthChecker(
    const server::handlers::auth::HandlerAuthConfig& auth_config
) const {
    return std::make_shared<
        AuthChecker>(digest_auth_settings_, auth_config["realm"].As<std::string>({}), secdist_.Get(), pg_cluster_);
}

}  // namespace samples::digest_auth
